import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import { Config, ClaudeRequest, BridgeError, APIError } from './types.js';
import { ApiConverter } from './converter.js';
import { ConfigManager } from './config.js';

export class BridgeServer {
  private app: express.Application;
  private converter: ApiConverter;
  private server?: ReturnType<typeof express.prototype.listen>;
  private isShuttingDown = false;

  constructor(private config: Config) {
    this.app = express();
    this.converter = new ApiConverter(config);
    this.setupMiddleware();
    this.setupRoutes();
    this.setupErrorHandling();
    this.setupGracefulShutdown();
  }

  private setupGracefulShutdown(): void {
    const shutdown = (signal: string) => {
      if (this.isShuttingDown) {
        console.log(`🛑 Received ${signal} again, forcing exit...`);
        process.exit(1);
      }
      
      this.isShuttingDown = true;
      console.log(`🛑 Received ${signal}, shutting down gracefully...`);
      
      if (this.server) {
        this.server.close(() => {
          console.log('✅ Server closed');
          process.exit(0);
        });
        
        // Force exit after 5 seconds if graceful shutdown fails
        setTimeout(() => {
          console.log('⚠️ Forcing exit after timeout');
          process.exit(1);
        }, 5000);
      } else {
        process.exit(0);
      }
    };

    process.on('SIGTERM', () => shutdown('SIGTERM'));
    process.on('SIGINT', () => shutdown('SIGINT'));
  }

  private setupMiddleware(): void {
    // CORS
    this.app.use(cors({
      origin: '*',
      methods: ['GET', 'POST', 'OPTIONS'],
      allowedHeaders: ['Content-Type', 'Authorization', 'anthropic-version'],
    }));

    // JSON parsing
    this.app.use(express.json({ limit: '10mb' }));

    // Request logging
    this.app.use((req: Request, _res: Response, next: NextFunction) => {
      const timestamp = new Date().toISOString();
      const logLevel = this.config.logLevel;
      
      if (logLevel === 'debug') {
        console.log(`[${timestamp}] ${req.method} ${req.path} - ${req.ip}`, {
          headers: req.headers,
          body: req.body ? JSON.stringify(req.body).substring(0, 500) + '...' : 'empty',
        });
      } else {
        console.log(`[${timestamp}] ${req.method} ${req.path} - ${req.ip}`);
      }
      next();
    });
  }

  private setupRoutes(): void {
    // Health check endpoints
    this.app.get('/health', (_req: Request, res: Response) => {
      res.json({
        status: 'healthy',
        timestamp: new Date().toISOString(),
        version: '1.0.0',
        config: {
          model: this.config.model,
          url: this.config.url,
          port: this.config.port,
          maxTokens: this.config.maxTokens,
        },
      });
    });

    this.app.get('/health/ready', (_req: Request, res: Response) => {
      res.json({
        status: 'ready',
        timestamp: new Date().toISOString(),
      });
    });

    this.app.get('/health/live', (_req: Request, res: Response) => {
      res.json({
        status: 'alive',
        timestamp: new Date().toISOString(),
      });
    });

    // Root endpoint - show usage information
    this.app.get('/', (_req: Request, res: Response) => {
      res.json({
        name: 'Claude Bridge',
        version: '1.0.0',
        description: 'A proxy server that enables Claude Code CLI to work with OpenAI-compatible API providers',
        endpoints: {
          health: '/health',
          messages: '/v1/messages',
        },
        usage: ConfigManager.getQuickStartInstructions(this.config),
      });
    });

    // Claude messages endpoint - main proxy functionality
    // Support both /v1/messages and /anthropic/v1/messages for different Claude CLI versions
    const handleMessages = async (req: Request, res: Response, next: NextFunction) => {
      try {
        const claudeRequest = req.body as ClaudeRequest;
        
        // Log incoming request
        if (this.config.logLevel === 'debug') {
          console.log('📨 Received Claude request:', {
            model: claudeRequest.model,
            messageCount: claudeRequest.messages?.length || 0,
            hasTools: !!claudeRequest.tools?.length,
            streaming: claudeRequest.stream,
            maxTokens: claudeRequest.max_tokens,
            messages: claudeRequest.messages?.map(m => ({
              role: m.role,
              contentType: typeof m.content,
              contentLength: typeof m.content === 'string' ? m.content.length : m.content?.length || 0,
            })),
          });
        } else {
          console.log('📨 Received Claude request:', {
            model: claudeRequest.model,
            messageCount: claudeRequest.messages?.length || 0,
            hasTools: !!claudeRequest.tools?.length,
            streaming: claudeRequest.stream,
            maxTokens: claudeRequest.max_tokens,
          });
        }

        // Validate request
        if (!claudeRequest.messages || !Array.isArray(claudeRequest.messages)) {
          throw new BridgeError('Invalid request: messages array is required', 'INVALID_REQUEST', 400);
        }

        // Convert to OpenAI format
        const openaiRequest = this.converter.claudeToOpenAI(claudeRequest);

        // Make request to target API
        const openaiResponse = await this.converter.makeRequest(openaiRequest);

        // Convert back to Claude format
        const claudeResponse = this.converter.openAIToClaude(openaiResponse, claudeRequest.model);

        console.log('✅ Returning Claude response:', {
          id: claudeResponse.id,
          contentItems: claudeResponse.content.length,
          stopReason: claudeResponse.stop_reason,
          usage: claudeResponse.usage,
        });

        res.json(claudeResponse);
      } catch (error) {
        next(error);
      }
    };

    // Register the handler for both endpoints
    this.app.post('/v1/messages', handleMessages);
    this.app.post('/anthropic/v1/messages', handleMessages);

    // Catch-all for unsupported endpoints
    this.app.all('*', (req: Request, res: Response) => {
      res.status(404).json({
        error: {
          type: 'not_found',
          message: `Endpoint ${req.method} ${req.path} not found`,
        },
      });
    });
  }

  private setupErrorHandling(): void {
    // eslint-disable-next-line no-unused-vars
    this.app.use((error: Error, req: Request, res: Response, _next: NextFunction) => {
      console.error('❌ Error occurred:', {
        message: error.message,
        stack: error.stack,
        path: req.path,
        method: req.method,
      });

      if (error instanceof BridgeError) {
        res.status(error.statusCode).json({
          error: {
            type: error.code.toLowerCase(),
            message: error.message,
          },
        });
        return;
      }

      if (error instanceof APIError) {
        res.status(error.statusCode).json({
          error: {
            type: 'api_error',
            message: error.message,
          },
        });
        return;
      }

      // Generic error
      res.status(500).json({
        error: {
          type: 'internal_error',
          message: 'An internal error occurred',
        },
      });
    });
  }

  async start(): Promise<void> {
    return new Promise((resolve, reject) => {
      this.server = this.app.listen(this.config.port, this.config.host, () => {
        console.log(ConfigManager.getQuickStartInstructions(this.config));
        resolve();
      });

      this.server.on('error', (error: Error & { code?: string }) => {
        if ('code' in error && error.code === 'EADDRINUSE') {
          reject(new Error(`Port ${this.config.port} is already in use`));
        } else {
          reject(error);
        }
      });
    });
  }
}